#include "xtypes.h"
#include "xaux.h"
#include "util_httpserver.h"
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <assert.h>
#include <process.h>

// ScanForChar() - This function is similar to the CRT's strchr(), but this function
// specifies a length as well (so the string is not NULL terminated).
char* ScanForChar(char* startPos, size_t length, char charToFind)
{
	char*	pos		= startPos;
	size_t	remain	= length;

	while (remain > 0 && *pos != charToFind)
	{
		--remain;
		++pos;
	}

	if (remain == 0)
		return NULL;

	return pos;
}


// This table hold the status codes that we will report to the client.  These codes are used
// to build the status line of the response.
// These codes and descriptions were taken from the HTTP 1.0 RFC.
HttpStatus g_httpStatusTable[] =
{
	//	code							textCode		description
	{	HTTPCODE_OK,					"200",			"OK"								},
	{	HTTPCODE_CREATED,				"201",			"Created"							},
	{	HTTPCODE_ACCEPTED,				"202",			"Accepted"							},
	{	HTTPCODE_NO_CONTENT,			"204",			"No Content"						},
	{	HTTPCODE_MOVED_PERMANENTLY,		"301",			"Moved Permanently"					},
	{	HTTPCODE_MOVED_TEMPORARILY,		"302",			"Moved Temporarily"					},
	{	HTTPCODE_NOT_MODIFIED,			"304",			"Not Modified"						},
	{	HTTPCODE_BAD_REQUEST,			"400",			"Bad Request"						},
	{	HTTPCODE_UNAUTHORIZED,			"401",			"Unauthorized"						},
	{	HTTPCODE_FORBIDDEN,				"403",			"Forbidden"							},
	{	HTTPCODE_NOT_FOUND,				"404",			"Not Found"							},
	{	HTTPCODE_INTERNAL_SERVER_ERROR,	"500",			"Internal Server Error"				},
	{	HTTPCODE_NOT_IMPLEMENTED,		"501",			"Not Implemented"					},
	{	HTTPCODE_BAD_GATEWAY,			"502",			"Bad Gateway"						},
	{	HTTPCODE_SERVICE_UNAVAILABLE,	"503",			"Service Unavailable"				},
};

// FindHttpStatus() - Given a code, this function looks up the entry in the g_httpStatusTable table.
HttpStatus* FindHttpStatus(int code)
{
	// Calculate the number of entries in the table.
	size_t countOfItems = sizeof(g_httpStatusTable)/sizeof(g_httpStatusTable[0]);

	// Scan the table, looking for the code.
	for (size_t i = 0; i < countOfItems; ++i)
	{
		// If we find the code, return a pointer to the row in the table.
		if (g_httpStatusTable[i].code == code)
			return &g_httpStatusTable[i];
	}

	// We didn't find the code, so return NULL.
	return NULL;
}

// AddHeaderToCollection() - Adds a header to the specified HeaderCollection.  
// Headers are stored in a linked list.
BOOL AddHeaderToCollection(HeaderCollection* collection, Header* header)
{
	Header*	curHeader = collection->head;

	if (!curHeader)
	{
		// If the list is empty, just add the header to the head of the list
		// and initialize the count.
		collection->head	= header;
		collection->count	= 1;

		header->next = NULL;

		return TRUE;
	}

	// Check against the head of the list for a duplicate header.  If they have the same name,
	// return an error.
	if (_stricmp(curHeader->name, header->name) == 0)
		return FALSE;

	// Scan through the list, checking for duplicates, and looking for the end of the list.
	while (curHeader->next)
	{
		// Check for duplicate names.
		if (_stricmp(curHeader->next->name, header->name) == 0)
			return FALSE;

		curHeader = curHeader->next;
	}

	// We're at the end of the list, and there were no duplicates, so append the header to the list.
	curHeader->next = header;

	// Adjust the count.
	++collection->count;

	header->next = NULL;

	return TRUE;
}

// FindHeaderInCollection() - Search for a particular header, given the name.
Header* FindHeaderInCollection(HeaderCollection* collection, char* name)
{
	Header* curHeader = collection->head;

	// Scan through the list.
	while (curHeader)
	{
		// If the names match, return the pointer to the header.
		if (_stricmp(curHeader->name, name) == 0)
			return curHeader;

		curHeader = curHeader->next;
	}

	// We didn't find the header, so return NULL.
	return NULL;
}

// RemoveHeaderFromCollection() - Find the header, given the name, and remove it from the list.
BOOL RemoveHeaderFromCollection(HeaderCollection* collection, char* name)
{
	Header*	curHeader	= collection->head;
	Header* toDelete	= NULL;

	// If the list is empty, there is nothing to remove.
	if (!curHeader)
		return FALSE;

	// If the header to remove is the first node, just remove it.
	if (_stricmp(curHeader->name, name) == 0)
	{
		collection->head = collection->head->next;
		--collection->count;

		return TRUE;
	}

	// Scan through the list, looking for the header to remove.
	while (curHeader->next)
	{
		if (_stricmp(curHeader->next->name, name) == 0)
		{
			// The names match, so remove the header.
			toDelete		= curHeader->next;
			curHeader->next = curHeader->next->next;

			DestroyHeader(toDelete);

			--collection->count;

			return TRUE;
		}
	}

	// We didn't find a header with a matching name, so return FALSE.
	return FALSE;
}

// CreateHeaderCollection() - Creates a new header collection.
HeaderCollection* CreateHeaderCollection()
{
	HeaderCollection* hc = new HeaderCollection;

	AUX_ZERO_MEMORY(hc, sizeof(HeaderCollection));

	return hc;
}

// DestroyHeaderCollection() - Destroys a header collection
void DestroyHeaderCollection(HeaderCollection* collection)
{
	if (collection)
		CleanupHeaderCollection(collection);

	delete collection;
}

// CleanupHeaderCollection() - Removes all headers from the collection.
void CleanupHeaderCollection(HeaderCollection* collection)
{
	assert(collection);

	Header* curHeader		= collection->head;
	Header* headerToDelete	= NULL;

	while (curHeader)
	{
		headerToDelete = curHeader;

		curHeader = curHeader->next;

		DestroyHeader(headerToDelete);
	}

	collection->head	= NULL;
	collection->count	= 0;
}

// ValidateHeader() - Validate the header's values against the maximum lengths
BOOL ValidateHeader(char* name, char* value)
{
	if (strlen(name) > MAX_HEADER_NAME_LENGTH)
		return FALSE;

	if (strlen(value) > MAX_HEADER_VALUE_LENGTH)
		return FALSE;

	return TRUE;
}

// CreateHeader() - Creates a new header, and sets the values (if specified).
Header* CreateHeader(char* name, char* value)
{
	assert((name && value) || (!name && !value));

	// If the name and value were specified, validate them against the max lengths
	if (name && value && !ValidateHeader(name, value))
		return NULL;

	Header* header = new Header;

	AUX_ZERO_MEMORY(header, sizeof(Header));

	// Set the name (if specified).
	if (name)
		strncpy(header->name, name, MAX_HEADER_NAME_LENGTH);

	// Set the value (if specified).
	if (value)
		strncpy(header->value, value, MAX_HEADER_VALUE_LENGTH);

	// Always make sure the name and value are NULL terminated.
	header->name[MAX_HEADER_NAME_LENGTH]	= 0;
	header->value[MAX_HEADER_VALUE_LENGTH]	= 0;

	// Return the pointer to the new header.
	return header;
}

// DestroyHeader() - Destroys a header.
void DestroyHeader(Header* header)
{
	// Free the header.
	delete header;
}



// IsEndOfRequest() - Checks if the current position is the beginning of the end of request marker (\r\n\r\n).
BOOL IsEndOfRequest(char* startPos, size_t length)
{
	BYTE endOfRequest[] = { '\r', '\n', '\r', '\n' };

	// If the length is less than the length of the marker, then it isn't the marker.
	if (length < sizeof(endOfRequest))
		return FALSE;

	// Use memcmp to compare against the marker.  We don't use strcmp here, because we aren't necessarily using
	// NULL terminated strings.
	if (memcmp(startPos, endOfRequest, sizeof(endOfRequest)) != 0)
		return FALSE;

	// If we get here, then the comparison succeeded, so the current position is pointing to an
	// end of request marker.
	return TRUE;
}

// IsEndOfHeader() - Checks if the current position is the beginning of the end of header marker (\r\n),
//	taking into consideration multi-line headers (\r\n followed by a whitespace character).
BOOL IsEndOfHeader(char* startPos, size_t length)
{
	char multiLine1[]	= { '\r', '\n', ' ' };
	char multiLine2[]	= { '\r', '\n', '\t' };
	char endOfHeader[]	= { '\r', '\n' };

	// If the length is less than the length of the marker, then it isn't the marker.
	if (length < sizeof(endOfHeader))
		return FALSE;

	assert(sizeof(multiLine1) == sizeof(multiLine2));

	// If the length is greater than or equal to the length of a multi-line marker, then
	// we need to check if it might be a multi-line marker.
	if (length >= sizeof(multiLine1))
	{
		// Use memcmp, because we aren't necessarily using NULL terminated strings.
		if (memcmp(startPos, multiLine1, sizeof(multiLine1)) == 0 ||
			memcmp(startPos, multiLine2, sizeof(multiLine2)) == 0)
		{
			// It's a multi-line marker, so there is more to the header.
			return FALSE;
		}
	}

	// Use memcmp, because we aren't necessarily using NULL terminated strings.
	if (memcmp(startPos, endOfHeader, sizeof(endOfHeader)) != 0)
		return FALSE;

	// If we get here, then the comparison succeeded, so the current position is pointer to an
	// end of header marker.
	return TRUE;
}

// IsMultiLineSeperator() - Checks if the current position is the beginning of a multi-line marker 
// (\r\n followed by a whitespace character).
BOOL IsMultiLineSeperator(char* startPos, size_t length)
{
	char multiLine1[]	= { '\r', '\n', ' ' };
	char multiLine2[]	= { '\r', '\n', '\t' };

	assert(sizeof(multiLine1) == sizeof(multiLine2));

	// If the length is less than the length of the multi-line markers, then it isn't one of them.
	if (length < sizeof(multiLine1))
		return FALSE;

	// Use memcmp, because we aren't necessarily using NULL terminated strings.
	if (memcmp(startPos, multiLine1, sizeof(multiLine1)) == 0 ||
		memcmp(startPos, multiLine2, sizeof(multiLine2)) == 0)
	{
		// It's a multi-line marker, return TRUE;
		return TRUE;
	}

	// Not a multi-line marker (the above comparison's failed).
	return FALSE;
}

// GetMultiLineSeperatorLength() - Returns the length of the multi-line seperator.  If the current position
// is not a multi-line seperator, the results are undefined.
size_t GetMultiLineSeperatorLength(char* startPos, size_t length)
{
	assert(IsMultiLineSeperator(startPos, length));

	// Currently we only support two multi-line seperators, both 3 characters long.
	return 3;
}

xResult CheckForEndOfRequest(char* startPos, size_t length, BOOL *endOfRequest)
{
	char*	curPos	= startPos;
	size_t	remain	= length;

	*endOfRequest = FALSE;

	curPos = ScanForChar(curPos, remain, '\r');
	while (curPos)
	{
		remain = length-(curPos-startPos);

		*endOfRequest = IsEndOfRequest(curPos, remain);
		if (*endOfRequest)
			break;

		++curPos;
		--remain;

		curPos = ScanForChar(curPos, remain, '\r');
	}

	return XR_OK;
}

char* FindEndOfHeader(char* startPos, size_t length)
{
	char*	carriageReturn	= NULL;
	char*	curPos			= startPos;
	size_t	remain			= length;
	size_t	headerLength	= 0;

	for (;;)
	{
		carriageReturn = ScanForChar(curPos, remain, '\r');
		if (!carriageReturn)
			return NULL;

		headerLength	+= carriageReturn-curPos;
		remain			-= carriageReturn-curPos;

		curPos = carriageReturn;

		if (IsEndOfHeader(curPos, remain))
			return curPos;

		++headerLength;
		++curPos;
		--remain;
	}
}

xResult CountHeaders(char* startPos, size_t length, size_t* headerCount)
{
	char*	endOfHeader		= NULL;
	char*	curPos			= startPos;
	size_t	remain			= length;
	size_t	headerLength	= 0;

	*headerCount = 0;

	// Check for NULL headers
	if (remain == 2 && IsEndOfHeader(startPos, 2))
		return XR_OK;

	while (remain > 0)
	{
		endOfHeader = FindEndOfHeader(curPos, remain);
		if (!endOfHeader)
			return XR_FAIL;

		++(*headerCount);

		headerLength = endOfHeader-curPos;

		if (IsEndOfRequest(endOfHeader, remain-headerLength))
			break;

		remain -= headerLength+2;
		curPos += headerLength+2;
	}

	return XR_OK;
}

xResult StripMultiLineSeperators(Header* header)
{
	xResult hr				= XR_OK;
	char*	start			= header->value;
	char*	curPos			= start;
	size_t	remain			= strlen(start);
	size_t	multiLineLength	= 0;

	while (remain > 0)
	{
		curPos = ScanForChar(start, remain, '\r');
		if (!curPos)
			return XR_OK;

		if (IsMultiLineSeperator(curPos, remain))
		{
			remain -= curPos-start;

			multiLineLength = GetMultiLineSeperatorLength(curPos, remain);

			memcpy(curPos, curPos+multiLineLength, remain-multiLineLength);
		}

		start = curPos;
	}

	return hr;
}

Request* AllocateRequest()
{
	return new Request;
}

void FreeRequest(Request* request)
{
	delete request;
}

void CleanupRequest(Request* request)
{
	CleanupHeaderCollection(&request->headers);

	AUX_ZERO_MEMORY(&request->uri, sizeof(Uri));

	request->verb		= VERB_UNKNOWN;
	request->version	= HTTP_10;
}

xResult ParseVerb(char* buffer, size_t length, size_t* verbLength, int* verb)
{
	if (length >= 4 &&
		_strnicmp((char*)buffer, "get ", 4) == 0)
	{
		*verbLength = 3;
		*verb		= VERB_GET;

		return XR_OK;
	}

	return XR_FAIL;
}

xResult ParseVersion(char* buffer, size_t length, size_t* versionLength, int* version)
{
	if (length >= 8 &&
		_strnicmp((char*)buffer, "http/1.0", 8) == 0)
	{
		*versionLength	= 8;
		*version		= HTTP_10;

		return XR_OK;
	}
	else if (length >= 8 &&
		_strnicmp((char*)buffer, "http/1.1", 8) == 0)
	{
		*versionLength	= 8;
		*version		= HTTP_11;

		return XR_OK;
	}
	
	return XR_FAIL;
}

xResult ParseUri(char* string, Uri* uri)
{
	size_t	uriLength	= strlen(string);
	char*	queryMarker	= NULL;
	size_t	pathLength	= 0;
	size_t	queryLength	= 0;

	// find the query marker in the uri
	queryMarker = ScanForChar(string, uriLength, '?');
	if (!queryMarker)
	{
		if (uriLength > MAX_URI_PATH)
			return XR_FAIL;

		memcpy(uri->path, string, uriLength);
		uri->path[uriLength] = 0;

		uri->query[0] = 0;
	}
	else
	{
		pathLength = queryMarker-string;

		if (pathLength > MAX_URI_PATH)
			return XR_FAIL;

		memcpy(uri->path, string, pathLength);
		uri->path[pathLength] = 0;

		++queryMarker;

		queryLength = uriLength-pathLength-1;
		if (queryLength > MAX_URI_QUERY)
			return XR_FAIL;

		memcpy(uri->query, queryMarker, queryLength);
		uri->query[queryLength] = 0;
	}

	return XR_OK;
}

xResult ParseHeader(char* startPos, size_t length, Header* header, size_t* headerLength)
{
	xResult	hr				= XR_OK;
	char*	curPos			= startPos;
	size_t	remain			= length;
	char*	endOfHeader		= NULL;
	size_t	nameLength		= 0;
	size_t	valueLength		= 0;
	char*	colon			= NULL;

	endOfHeader = FindEndOfHeader(curPos, remain);
	if (!endOfHeader)
		return XR_FAIL;

	*headerLength = endOfHeader-curPos;

	colon = ScanForChar(curPos, *headerLength, ':');
	if (!colon)
		return XR_FAIL;

	nameLength = colon-curPos;
	if (nameLength > MAX_HEADER_NAME_LENGTH)
		return XR_FAIL;

	memcpy(header->name, curPos, nameLength);
	header->name[nameLength] = 0;

	curPos += nameLength+1;
	remain -= nameLength+1;

	if (remain == 0)
		return XR_FAIL;

	if (*(curPos) != ' ' &&
		*(curPos) != '\t')
	{
		return XR_FAIL;
	}

	++curPos;
	--remain;

	valueLength = endOfHeader-curPos;
	if (valueLength > MAX_HEADER_VALUE_LENGTH)
		return XR_FAIL;

	memcpy(header->value, curPos, valueLength);
	header->value[valueLength] = 0;

	curPos += valueLength;
	remain -= valueLength;

	assert(IsEndOfHeader(curPos, remain));

	hr = StripMultiLineSeperators(header);
	if (XR_FAILED(hr))
		return hr;

	*headerLength += 2;

	return XR_OK;
}

xResult ParseRequest(BYTE* buffer, size_t bufferLength, Request* request)
{
	xResult	hr				= XR_OK;
	char*	bufferPos		= (char*)buffer;
	char*	scanPos			= NULL;
	size_t	scanLength		= 0;
	size_t	lengthRemain	= bufferLength;
	size_t	verbLength		= 0;
	size_t	versionLength	= 0;
	BOOL	hasVersion		= FALSE;
	size_t	headerLength	= 0;
	size_t	headerCount		= 0;
	char	uriTemp[MAX_URI_PATH+MAX_URI_QUERY+1];

	hr = ParseVerb(bufferPos, bufferLength, &verbLength, &request->verb);
	if (XR_FAILED(hr))
		return hr;

	bufferPos		+= verbLength;
	lengthRemain	-= verbLength;

	if (lengthRemain == 0)
		return XR_FAIL;

	// move past the space that seperates the verb and the uri
	++bufferPos;
	--lengthRemain;

	if (lengthRemain == 0)
		return XR_FAIL;

	// next we expect the uri.  Scan to the first space.
	scanPos = ScanForChar(bufferPos, lengthRemain, ' ');
	if (!scanPos)
	{
		// there is no space, so try to find the end of the line
		scanPos = ScanForChar(bufferPos, lengthRemain, '\r');
		if (!scanPos)
			return XR_FAIL;

		scanLength = scanPos-bufferPos;

		if (scanLength == lengthRemain)
			return XR_FAIL;

		if (*(scanPos+1) != '\n')
			return XR_FAIL;

		hasVersion = FALSE;
	}
	else
	{
		hasVersion = TRUE;
	}

	scanLength = scanPos-bufferPos;
	// scanPos points to the end of the uri
	if (scanLength > MAX_URI_PATH+MAX_URI_QUERY)
		return XR_FAIL;

	memcpy(uriTemp, bufferPos, scanLength);
	uriTemp[scanLength] = 0;

	hr = ParseUri(uriTemp, &request->uri);
	if (XR_FAILED(hr))
		return hr;

	bufferPos		+= scanLength;
	lengthRemain	-= scanLength;

	if (hasVersion)
	{
		// move past the space that seperates the uri and version field
		++bufferPos;
		--lengthRemain;

		hr = ParseVersion(bufferPos, lengthRemain, &versionLength, &request->version);
		if (XR_FAILED(hr))
			return hr;

        bufferPos		+= versionLength;
		lengthRemain	-= versionLength;
	}

	if (lengthRemain < 2)
		return XR_FAIL;

	if (*bufferPos != '\r' && *(bufferPos+1) != '\n')
		return XR_FAIL;

	bufferPos		+= 2;
	lengthRemain	-= 2;

	AUX_ZERO_MEMORY(&request->headers, sizeof(HeaderCollection));

	if (lengthRemain == 0)
		return XR_OK;

	hr = CountHeaders(bufferPos, lengthRemain, &headerCount);
	if (XR_FAILED(hr))
		goto Cleanup_Headers;

	if (headerCount > 0)
	{
		for (size_t i = 0; i < headerCount; ++i)
		{
			Header* newHeader = CreateHeader("", "");
			if (!newHeader)
				goto Cleanup_Headers;

			hr = ParseHeader(bufferPos, lengthRemain, newHeader, &headerLength);
			if (XR_FAILED(hr))
				goto Cleanup_Headers;

			if (!AddHeaderToCollection(&request->headers, newHeader))
				goto Cleanup_Headers;

			bufferPos		+= headerLength;
			lengthRemain	-= headerLength;

			assert(lengthRemain > 0 || i == headerCount);
		}
	}

	return XR_OK;

Cleanup_Headers:
	CleanupHeaderCollection(&request->headers);

	return hr;
}

BOOL IsKeepAlive(Request* request)
{
	Header* header = FindHeaderInCollection(&request->headers, "Connection");

	if (!header)
		return FALSE;

	if (_stricmp(header->value, "Keep-Alive") != 0)
		return FALSE;

	return TRUE;
}

size_t GetContentLength(Request* request)
{
	Header* header = FindHeaderInCollection(&request->headers, "Content-Length");
	if (!header)
		return 0;

	// protect against overflow
	if (strlen(header->value) > 9)
		return 0;

	int length = atoi(header->value);

	// protect against negative values
	if (length < 0)
		return 0;

	return length;
}




// CreateResponse() - Create a new response structure.
//  DEVNOTE: this could be extended later to cache the Response structures, to minimize
//		contention on the default CRT heap.
Response* CreateResponse()
{
	Response* response = new Response;

	// Zero out the structure fields.
	AUX_ZERO_MEMORY(response, sizeof(Response));

	return response;
}

// DestroyResponse() - Destroy the response structure.
//  DEVNOTE: this could be extended later to cache the Response structures, to minimize
//		contention on the default CRT heap.
void DestroyResponse(Response* response)
{
	if (response)
	{
		// Cleanup the response.
		CleanupResponse(response);

		// Free the memory.
		delete[] response;
	}
}

// CleanupResponse() - Cleanup the response structure, either before destroying it, or when reusing it.
void CleanupResponse(Response* response)
{
	assert(response);

	// Cleanup the response's header collection.
	CleanupHeaderCollection(&response->headers);
}

// SetContentLength() - Sets the "Content-Length" header in the response.
BOOL SetContentLength(Response* response, size_t length)
{
	char lengthText[32];

	// Convert the length parameter to a string (_snprintf allows us to specify a max length)
	if (_snprintf(lengthText, sizeof(lengthText), "%d", length) <= 0)
		return FALSE;

	// Create the header, and set the name and value.
	Header* header = CreateHeader("Content-Length", lengthText);
	if (!header)
		return FALSE;

	// Add the header to the collection.
	return AddHeaderToCollection(&response->headers, header);
}

// SetHeader() - Sets the "Server" header in the response.
BOOL SetServer(Response* response, char* server)
{
	// Create the header, and set the name and value.
	Header* header = CreateHeader("Server", server);
	if (!header)
		return FALSE;

	// Add the header to the collection.
	return AddHeaderToCollection(&response->headers, header);
}

// SetContentType() - Sets the "Content-Type" header in the response.
BOOL SetContentType(Response* response, char* type)
{
	// Create the header, and set the name and value.
	Header* header = CreateHeader("Content-Type", type);
	if (!header)
		return FALSE;

	// Add the header to the collection.
	return AddHeaderToCollection(&response->headers, header);
}

// SetConnection() - Sets the "Connection" header in the response.
BOOL SetConnection(Response* response, char* type)
{
	// Create the header, and set the name and value.
	Header* header = CreateHeader("Connection", type);
	if (!header)
		return FALSE;

	// Add the header to the collection.
	return AddHeaderToCollection(&response->headers, header);
}













